﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using EveMarketsDAO;

namespace CoreEveMarkets
{
	public class Calculer
	{
		#region Attributs
		private ProfilDll _profilProducteur;
		private bool _tech1;
		private bool _tech2;
		private readonly SortedDictionary<int, decimal> _listeDePrix; 

		// <idRegion, idSystem, idStation>
		private Tuple<int, int, int> _pointDeVente;
		private Tuple<int, int, int> _pointAchat;

		#endregion

		#region Get & Set
		
		public ProfilDll ProfilProducteur
		{
			get { return _profilProducteur; }
			set { _profilProducteur = value; }
		}

		public bool Tech1
		{
			get { return _tech1; }
			set { _tech1 = value; }
		}

		public bool Tech2
		{
			get { return _tech2; }
			set { _tech2 = value; }
		}

		public Tuple<int, int, int> PointDeVente
		{
			get { return _pointDeVente; }
			set { _pointDeVente = value; }
		}

		public Tuple<int, int, int> PointAchat
		{
			get { return _pointAchat; }
			set { _pointAchat = value; }
		}

		private SortedDictionary<int, decimal> ListeDePrix
		{
			get { return _listeDePrix; }
		}

		#endregion

		#region Constructeur
		/// <summary>
		/// Constructeur pour lancer un calcul sur les Technologies.
		/// </summary>
		/// <param name="checkTech1">booléen pour le Tech1</param>
		/// <param name="checkTech2">booléen pour le Tech2</param>
		/// <param name="achat">Station d'achat</param>
		/// <param name="vente">Station de vente</param>
		public Calculer(bool checkTech1, bool checkTech2, string achat, string vente)
		{
			Tech1 = checkTech1;
			Tech2 = checkTech2;

			// Détermination des ID -région,system,station- de vente et Achat.
			PointAchat = RecupererIds(Convert.ToInt32(achat));
			PointDeVente = RecupererIds(Convert.ToInt32(vente));
			_listeDePrix = new SortedDictionary<int, decimal>();
		}

		/// <summary>
		/// Constructeur pour lancer un calcul pour un Blueprint unitaire.
		/// </summary>
		/// <param name="achat"></param>
		/// <param name="vente"></param>
		public Calculer(string achat, string vente)
		{
			// Je prend le profil.
			ProfilProducteur = ProfilProvider.GetProfil();

			// Détermination des ID -région,system,station- de vente et Achat.
			PointAchat = RecupererIdsParRapportNomStation(achat);
			PointDeVente = RecupererIdsParRapportNomStation(vente);

			_listeDePrix = new SortedDictionary<int, decimal>();
		}
		#endregion

		#region Méthodes Private
		/// <summary>
		/// Méthode qui permet de chercher les ids -Région, System,Station -
		/// </summary>
		/// <param name="idStation">ID de la station</param>
		/// <returns>retourne ID région, ID système, ID station</returns>
		private Tuple<int, int, int> RecupererIds(int idStation)
		{
			// Je récupère les IDs de la station donnée
			var station = (from ii in ContexteDAO.ContexteDonnees.staStations
						   where ii.stationID == idStation
						   select ii).First();

			Tuple<int, int, int> retourIds = new Tuple<int, int, int>((int)station.regionID, (int)station.solarSystemID, station.stationID);
			return retourIds;
		}

		/// <summary>
		/// Méthode qui permet de chercher les ids -Région, System,Station -
		/// </summary>
		/// <param name="nomStation">Nom de la station</param>
		/// <returns>retourne ID région, ID système, ID station</returns>
		private Tuple<int, int, int> RecupererIdsParRapportNomStation(string nomStation)
		{
			// Je récupère les IDs de la station donnée
			var station = (from ii in ContexteDAO.ContexteDonnees.staStations
						   where ii.stationName == nomStation
						   select ii).First();

			Tuple<int, int, int> retourIds = new Tuple<int, int, int>((int)station.regionID, (int)station.solarSystemID, station.stationID);
			return retourIds;
		}

		/// <summary>
		/// Traitement pour un blueprint.
		/// </summary>
		/// <param name="blueprintAtraiter"></param>
		/// <param name="dateProduction"></param>
		/// <returns></returns>
		private Tuple<Resultats, List<ResultatsDetails>> TraitementUnBLueprint(LesBlueprints blueprintAtraiter, DateTime dateProduction)
		{
			MeilleurPrix moinsChere = new MeilleurPrix(PointAchat, PointDeVente);
			var industryCalcul = new IndustryCalcul(ProfilProvider.GetProfil().SkillProdEfficiency);
			var indusTime = new IndustryCalcul(ProfilProvider.GetProfil().SkillIndustry);
			var listeComposantsDuBluePrintManufacture = ContexteDAO.FonctionCalculateur.GetListComposant(blueprintAtraiter.IdBlueprint);
			double chanceDeReussite = 0;

			// Je vais chercher le facteur production efficiency et matériel. Pour le Tech2, je met à -4. Pour le reste je regarde dans la table des blueprints
			// utilisateur s'il existe, sinon je met à 0.
			int prodEfficiency = blueprintAtraiter.TechLevel == 2 ? -4 : ContexteDAO.FonctionCalculateur.GetProdEfficiency(blueprintAtraiter.IdBlueprint);
			int materielEfficiency = blueprintAtraiter.TechLevel == 2 ? -4 : ContexteDAO.FonctionCalculateur.GetMaterielEfficiency(blueprintAtraiter.IdBlueprint);

			// Recherche de combien d'élément est produit pour 1 Run.
			int nbreElement =
					ContexteDAO.ContexteDonnees.RunBlueprint.Where(bp => bp.IdBlueprint.Equals(blueprintAtraiter.IdBlueprint))
																							.Select(elem => elem.NombreParRun).DefaultIfEmpty(1).First();

			List<ResultatsDetails> listDetail = new List<ResultatsDetails>();
			bool boolBreak = false;
			decimal prixTotal = Decimal.Zero;
			decimal prixTotalInvention = Decimal.Zero;
			// Check si c'est de la catégorie Drone.
			
			#region Temps Production

			// Nous devons recalculer le temps de production.
			int tempsProd = indusTime.NouveauTempsProd(prodEfficiency, blueprintAtraiter.ProductionTime, blueprintAtraiter.ProductivityModifier);
			#endregion

			/* Sur certains BluePrint, il y a des composants en double (exemple Morphite). La nuance vient du fait qu'un des composants est soumis au
			* Waste, l'autre non. Par contre, il faut additionner les 2 quantités après le calcul des nouvelles quantités.
			*/
			bool presenceDoublon = (from dd in listeComposantsDuBluePrintManufacture
									group dd by dd.ComposantId into grps
									where (grps.Count() >= 2)
									select grps).Count() > 0;

			// Maintenant, pour chaque compo, je dois faire le traitement.
			foreach (var composantsBlueprint in listeComposantsDuBluePrintManufacture)
			{
				#region calcul nouvelle quantitée.
				decimal newQuantite;

				// Je dois appliquer en fonction des skills du joueurs et du ME une modification des quantités.
				if (ContexteDAO.FonctionCalculateur.IsDroneBlueprint(blueprintAtraiter))
				{
					newQuantite = industryCalcul.NouvelleQuantitePourDrone(composantsBlueprint.Quantite, materielEfficiency, composantsBlueprint.Wasted,
																		   blueprintAtraiter.WasteFactor, ContexteDAO.FonctionCalculateur.IsComposantMineral(composantsBlueprint));
				}
				else
				{
					newQuantite = industryCalcul.NouvelleQuantiteAutreQueDrone(composantsBlueprint.Quantite, materielEfficiency,
																			   composantsBlueprint.Wasted, blueprintAtraiter.WasteFactor);
				} 

				// Ici je dois appliquer une réduction si pour un Run, je sors plus d'un élément (exemple : les munitions).
				newQuantite = newQuantite / nbreElement;

				#endregion

				#region Achat des composants.

				decimal prix;
				// Si nous connaissons le prix, nous le récupérons.
				if (ListeDePrix.ContainsKey(composantsBlueprint.ComposantId))
				{
					prix = ListeDePrix[composantsBlueprint.ComposantId];
				}
				else
				// Sinon, nous allons le chercher.
				{
					prix = moinsChere.PasCherePasChere(composantsBlueprint.ComposantId);
					ListeDePrix.Add(composantsBlueprint.ComposantId, prix);
				}

				// Si le prix est supérieur à zéro.
				if (prix > 0)
				{
					decimal prixFinal = newQuantite * prix;
					prixTotal += prixFinal;

					// Sauvegarde du calcul.
					ResultatsDetails detailCalculComposant = new ResultatsDetails
					{
						IdItem = blueprintAtraiter.ProductTypeId,
						IdStationAchat = _pointAchat.Item3,
						IdStationVente = _pointDeVente.Item3,
						DateCalcul = dateProduction,
						MaterielId = composantsBlueprint.ComposantId,
						Quantite = newQuantite,
						PrixUnitaire = prix,
						PrixTotal = prix * newQuantite,
					};

					// Si j'ai une présence de doublon, je dois regarder dans la liste (listDetail) si j'ai déjà la présence,
					// si oui, je met à jour, sinon je continue.
					if (presenceDoublon)
					{
						// Vérification de la présence.
						var line = listDetail.Where(x => x.IdItem.Equals(blueprintAtraiter.ProductTypeId) && x.MaterielId.Equals(composantsBlueprint.ComposantId));
						if (line.Count() > 0)
						{
							// Je dois additionner ce que je viens de déterminer avec 'calculDetail' dans l'objet trouvé.
							foreach (var resultatsDetailse in line)
							{
								resultatsDetailse.Quantite += detailCalculComposant.Quantite;
								resultatsDetailse.PrixTotal += detailCalculComposant.PrixTotal;
							}
						}
						else
						{
							listDetail.Add(detailCalculComposant);
						}
					}
					else
						listDetail.Add(detailCalculComposant);
				}
				else
				{
					// Si une valeur est à zéro, je ne dois pas continuer le calcul, je sors (break;).
					boolBreak = true;
					break;
				}
				#endregion
			}

			#region Invention

			if (blueprintAtraiter.MetaGroup.Equals("T2"))
			{
				// Attention, là nous sommes sur l'ID du blueprint Tech2, or les composants pour faire l'invention, se trouve sur
				// le blueprint du Tech1.

				// Trouver le BP Tech1. Valeur par défaut est 1, qui correspond qu'il n'y a pas de BP trouvé.
				var blueprintTech1Origine = ContexteDAO.ContexteDonnees.Invention
					.Where(y => y.InventIdBlueprint.Equals(blueprintAtraiter.IdBlueprint))
					.Select(x => x.IdBlueprint).DefaultIfEmpty(1).First();

				// Pas de BP trouvé, sortons du IF.
				if (!blueprintTech1Origine.Equals(1))
				{
					// Récupérer la liste de composant pour invention
					var listeCompoInvention = (from compo in ContexteDAO.ContexteDonnees.ComposantsBlueprint
											   where compo.IdBlueprint.Equals(blueprintTech1Origine)
													 && compo.ActivityId.Equals(8)
													 && compo.DamagePerJob.Equals(1)
											   select compo).ToList();
					
					// Trouvons quel est le pourcentage de réussite d'avoir un blueprint.
					Invention invent = new Invention(blueprintAtraiter.IdBlueprint, blueprintTech1Origine, listeCompoInvention);
					// NOTE : Pour l'instant je laisse les skills (par défaut) à 4.
					chanceDeReussite = invent.CalculPourcentageReussite();

					// Récupération du nombre de run par blueprint, pour pouvoir faire une estimation.
					var nbreRun = ContexteDAO.FonctionCalculateur.GetNbreRunByBlueprint(blueprintAtraiter.IdBlueprint);
					var nbrePerRun = ContexteDAO.FonctionCalculateur.GetNombreItemParRunByBlueprint(blueprintAtraiter.IdBlueprint);

					// Pour chaque composant (Datacore), il faut déterminer combien il est en faut pour 1 BP (en prenant en compte,
					// les datacores perdus).
					foreach (var dataCore in listeCompoInvention)
					{
						decimal prixDeLelement = Decimal.Zero;

						// Pour faire 1 BP Tech2.
						// Exemple : 30% de réussite donc (2 - 0.3) = 1.7
						// 1.7 --> 1 pour le '100%' de réussite, et le 0.7 est le surplus du au perte.
						var combienDataCoreParBp = dataCore.Quantite * (100 / (100 * chanceDeReussite));

						// Pour faite 1 Run.
						var combienPourLeRun = combienDataCoreParBp / nbreRun;

						// Maintenant pour faire 1 seul élément pour le run (exemple pour les drones, 1 run = 10 drones.)
						var combienPourUnElement = combienPourLeRun / nbrePerRun;

						// Achat du composant.
						if (ListeDePrix.ContainsKey(dataCore.ComposantId))
						{
							prixDeLelement = ListeDePrix[dataCore.ComposantId];
						}
						else
						{
							prixDeLelement = moinsChere.PasCherePasChere(dataCore.ComposantId);
							ListeDePrix.Add(dataCore.ComposantId, prixDeLelement);
						}

						// Si le prix est supérieur à zéro.
						if (prixDeLelement > 0)
						{
//							decimal test = Math.Round((decimal) combienPourUnElement, 2, MidpointRounding.ToEven); 
//							decimal prixFinal = test * prixDeLelement;
							decimal prixFinal = (decimal)combienPourUnElement * prixDeLelement;
							prixTotalInvention += prixFinal;

							// Sauvegarde du calcul.
							ResultatsDetails calculDetailInvention = new ResultatsDetails
							                                         	{
							                                         		IdItem = blueprintAtraiter.ProductTypeId,
							                                         		IdStationAchat = _pointAchat.Item3,
							                                         		IdStationVente = _pointDeVente.Item3,
							                                         		DateCalcul = dateProduction,
							                                         		MaterielId = dataCore.ComposantId,
																			//Quantite = Convert.ToDecimal(combienPourUnElement),
																			Quantite =  Math.Round((decimal) combienPourUnElement, 2, MidpointRounding.ToEven),
							                                         		PrixUnitaire = prixDeLelement,
							                                         		PrixTotal = prixFinal
							                                         	};
							listDetail.Add(calculDetailInvention);
						}
					}
				}
			}

			#endregion

			#region Vente de l'objet en sortie.

			// Je vais chercher le prix de vente de ce produit.
			decimal prixVente = moinsChere.PrixDeVente(blueprintAtraiter.ProductTypeId);

			#endregion

			#region Création du 'Resultats'
			// Création de l'objet.
			Resultats resultatCalcul = ContexteDAO.ContexteDonnees.Resultats.CreateObject();

			decimal testTemps = Decimal.Divide((decimal) tempsProd, (decimal) nbreElement);

			// Si le boolBreak est False, alors je sauvegarde.)
			if (!boolBreak && prixVente > 0)
			{
				resultatCalcul.IdItem = blueprintAtraiter.ProductTypeId;
				resultatCalcul.IdStationAchat = _pointAchat.Item3;
				resultatCalcul.IdStationVente = _pointDeVente.Item3;
				resultatCalcul.DateCalcul = dateProduction;
				resultatCalcul.CoutProd = prixTotal;
				resultatCalcul.PrixVente = prixVente;
				resultatCalcul.Benefice = prixVente - prixTotal - prixTotalInvention;
				resultatCalcul.TpsIsk = resultatCalcul.Benefice / testTemps;  // ((decimal)tempsProd / nbreElement);
				resultatCalcul.TempProd = tempsProd; // TODO : A voir, il faut mettre la prod pour 1 élément.
				resultatCalcul.TempInvention = 0; // TODO : A mettre en place
				resultatCalcul.CoutInvention = prixTotalInvention;
				if (!chanceDeReussite.Equals(0))
					resultatCalcul.PctReussite = Convert.ToDecimal(chanceDeReussite * 100);
			}

			return new Tuple<Resultats, List<ResultatsDetails>>(resultatCalcul, listDetail);

			#endregion
		}

		#endregion

		#region Méthodes Public
		/// <summary>
		/// En fonction des attributs de la classe, je fais du LinQ pour récupérer la liste de BP de Tech sélectionné.
		/// </summary>
		public void CalculerBenefice(BackgroundWorker worker)
		{
			// TODO : Mettre en place un traitement de suppression pour éviter les doublons.

			// Préparation pour la progressBar
			double pourcentageProgress = 0;
			double nbreTotalBlueprint = 0;
			if (Tech1)
				nbreTotalBlueprint += ContexteDAO.FonctionCalculateur.GetListBlueprint(1).Count();
			if(Tech2)
				nbreTotalBlueprint += ContexteDAO.FonctionCalculateur.GetListBlueprint(2).Count();
			// Valeur pour 1 BP en pourcent.
			double unBpEnPct = (1 / nbreTotalBlueprint) * 100;


			worker.ReportProgress(0, nbreTotalBlueprint + @" Blueprint(s) à calculer.");

			DateTime dateProduction = DateTime.Now;
			// Comme le Tech1 n'utilise que 6 types de ressources (les minerais), je vais commencer par eux.
			int compteurGeneral = 0;
			if (Tech1)
			{
				List<LesBlueprints> listBlueprintTech1 = ContexteDAO.FonctionCalculateur.GetListBlueprint(1);
				
				foreach (var blueprint in listBlueprintTech1)
				{
					ContexteDAO.FonctionsBasique.DeleteItemInResultats(blueprint.ProductTypeId, this.PointAchat.Item3, this.PointDeVente.Item3);
					var resultatCalcul = TraitementUnBLueprint(blueprint, dateProduction);
					ContexteDAO.FonctionCalculateur.SaveResultatsCalcul(resultatCalcul);

					// retour information pour la progressBar
					compteurGeneral ++;
					pourcentageProgress += unBpEnPct;
					worker.ReportProgress(Convert.ToInt32(pourcentageProgress), (nbreTotalBlueprint - compteurGeneral) + @" Blueprint(s) à calculer.");
				}
			}

			if (Tech2)
			{
				var listBlueprintTech2 = ContexteDAO.FonctionCalculateur.GetListBlueprint(2);
				foreach (var blueprintT2 in listBlueprintTech2)
				{
					ContexteDAO.FonctionsBasique.DeleteItemInResultats(blueprintT2.ProductTypeId, this.PointAchat.Item3, this.PointDeVente.Item3);
					var resultatTech2 = TraitementUnBLueprint(blueprintT2, dateProduction);
					ContexteDAO.FonctionCalculateur.SaveResultatsCalcul(resultatTech2);

					// retour information pour la progressBar
					compteurGeneral ++;
					pourcentageProgress += unBpEnPct;
					worker.ReportProgress(Convert.ToInt32(pourcentageProgress), (nbreTotalBlueprint - compteurGeneral) + @" Blueprint(s) à calculer.");
				}
			}

			// Fin
			worker.ReportProgress(100, @"Calcul Terminé.");
		}

		/// <summary>
		/// Cherche le Blueprint dans la table Blueprint, en fonction du nom du Blueprint passé en paramètre.
		/// </summary>
		/// <param name="nomBlueprint">Nom du blueprint à trouver.</param>
		public void CalculerBeneficeUnitaire(string nomBlueprint)
		{
			// Je supprime toutes les données avants, pour ne pas avoir de conflit de PK.
			// TODO : Faire un DELETE
			LesBlueprints leBlueprint = ContexteDAO.FonctionCalculateur.GetBlueprintByName(nomBlueprint);

			ContexteDAO.FonctionsBasique.DeleteItemInResultats(leBlueprint.ProductTypeId, this.PointAchat.Item3, this.PointDeVente.Item3);
			var resultatCalcul = TraitementUnBLueprint(leBlueprint, DateTime.Now);
			ContexteDAO.FonctionCalculateur.SaveResultatsCalcul(resultatCalcul);
		} 


		#endregion

	}
}
